package com.fingard.util;

import de.idyl.winzipaes.AesZipFileDecrypter;
import de.idyl.winzipaes.AesZipFileEncrypter;
import de.idyl.winzipaes.impl.AESDecrypter;
import de.idyl.winzipaes.impl.AESDecrypterBC;
import de.idyl.winzipaes.impl.AESEncrypter;
import de.idyl.winzipaes.impl.AESEncrypterBC;
import de.idyl.winzipaes.impl.ExtZipEntry;
import org.apache.commons.io.FileUtils;
import sun.misc.BASE64Decoder;

import java.io.*;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

public class ZipHelper {
	private static final int BUFFER_SIZE = 2048;
	
	/**
	 * 解压zip文件，带字符集参数
	 */
	public static void unZipFiles(String p_zipFile, String p_destDir, String pCharset)
			throws IOException {
		File destDir = new File(p_destDir);
		if (!destDir.exists()) {
			destDir.mkdirs();
		}
		ZipFile tmpZip = null;
		InputStream inStream = null;
		OutputStream outStream = null;
		try {

			tmpZip = new ZipFile(p_zipFile);

			for (Enumeration<? extends ZipEntry> zipEn = tmpZip.entries(); zipEn.hasMoreElements();) {
				inStream = null;
				outStream = null;
				ZipEntry eachEntry = (ZipEntry) zipEn.nextElement();
				String zipEntryName = eachEntry.getName();
				inStream = tmpZip.getInputStream(eachEntry);
				String savePath = (p_destDir + File.separator + zipEntryName).replace("\\", File.separator).replace("/", File.separator);
				int lastIdx = savePath.lastIndexOf(File.separator);
				if(lastIdx>0){
					File file = new File(savePath.substring(0, lastIdx));
					if(!file.exists()){
	    				file.mkdirs();
	    			}
				}
				File eachFile = new File(savePath);
				if (eachFile.isDirectory()) {
					continue;
				}
				outStream = new FileOutputStream(eachFile);
				byte[] buf1 = new byte[1024];
				int len;
				while ((len = inStream.read(buf1)) > 0) {
					outStream.write(buf1, 0, len);
				}
				inStream.close();
				inStream = null;
				outStream.close();
				outStream = null;
			}
		} catch (IOException ex) {
			throw (ex);
		} finally {
			if (inStream != null) {
				inStream.close();
			}
			if (outStream != null) {
				outStream.close();
			}
			if (tmpZip != null) {
				tmpZip.close();
			}
		}
	}

	/**
	 * 解压zip文件
	 */
	public static void unZipFiles(String p_zipFile, String p_destDir)
			throws IOException {
		File destDir = new File(p_destDir);
		if (!destDir.exists()) {
			destDir.mkdirs();
		}
		ZipFile tmpZip = null;
		InputStream inStream = null;
		OutputStream outStream = null;
		try {

			tmpZip = new ZipFile(p_zipFile);

			for (Enumeration<? extends ZipEntry> zipEn = tmpZip.entries(); zipEn
					.hasMoreElements();) {
				inStream = null;
				outStream = null;
				ZipEntry eachEntry = (ZipEntry) zipEn.nextElement();
				String zipEntryName = eachEntry.getName();
				inStream = tmpZip.getInputStream(eachEntry);
				String savePath = (p_destDir + File.separator + zipEntryName);
				// 判断路径是否存在,不存在则创建文件路径
				File eachFile = new File(savePath);
				File parentDir = new File(eachFile.getParent());
				if (!parentDir.exists()) {
					parentDir.mkdirs();
				}
				if (eachFile.isDirectory()) {
					if (!eachFile.exists()) {
						eachFile.mkdirs();
					}
					continue;
				}
				outStream = new FileOutputStream(eachFile);
				byte[] buf1 = new byte[1024];
				int len;
				while ((len = inStream.read(buf1)) > 0) {
					outStream.write(buf1, 0, len);
				}
				inStream.close();
				inStream = null;
				outStream.close();
				outStream = null;
			}
		} catch (IOException ex) {
			throw (ex);
		} finally {
			if (inStream != null) {
				inStream.close();
			}
			if (outStream != null) {
				outStream.close();
			}
			if (tmpZip != null) {
				tmpZip.close();
			}
		}
	}

	/**
	 * 解压缩zipFile(支持zip带目录)
	 * 
	 * @param zipFileName
	 *            要解压的zip文件对象
	 * @param outputDir
	 *            要解压到某个指定的目录下
	 * @throws Exception
	 */
	public static void unzipFile(String zipFileName, String outputDir) {
		try {
			BufferedOutputStream bos = null;

			BufferedInputStream bis = new BufferedInputStream(
					new FileInputStream(zipFileName));
			ZipInputStream zis = new ZipInputStream(bis);
//			ZipInputStream zis = new ZipInputStream(bis,Charset.forName("GBK"));
			ZipEntry entry;
			while ((entry = zis.getNextEntry()) != null) {
				int count;
				byte date[] = new byte[2048];
				if (entry.isDirectory()) {
					continue;
				} else {
					int begin = zipFileName.lastIndexOf(File.separator) + 1;
					int end = zipFileName.lastIndexOf(".");
					String zipRealName = zipFileName.substring(begin, end);

					String[] dirs = entry.getName().split("/",
							entry.getName().length());
					File file = new File(outputDir + File.separator + zipRealName);
					if (dirs.length > 1) {
						for (int i = 0; i < dirs.length - 1; i++) {
							// 根据file抽象路径和dir路径字符串创建一个新的file对象，路径为文件的上一个目录
							file = new File(file, dirs[i]);
						}
					}
					if (!file.exists()) {
						file.mkdirs();
					}
					file = new File(file, dirs[dirs.length - 1]);

					bos = new BufferedOutputStream(new FileOutputStream(file));
					while ((count = zis.read(date)) != -1) {
						bos.write(date, 0, count);
					}
					bos.flush();
					bos.close();
				}
			}
			zis.close();
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	/**
	 * 解压缩zipFile(支持zip带目录)
	 *
	 * @param zipFileName   要解压的zip文件对象
	 * @param outputDir     要解压到某个指定的目录下
	 *  @param outputDir    是否解压到当前文件夹
	 *
	 * @throws Exception
	 */
	public static void unzipFile(String zipFileName, String outputDir,boolean isCurDir) {
		try {
			BufferedOutputStream bos = null;

			BufferedInputStream bis = new BufferedInputStream(
					new FileInputStream(zipFileName));
			ZipInputStream zis = new ZipInputStream(bis);
//			ZipInputStream zis = new ZipInputStream(bis,Charset.forName("GBK"));
			ZipEntry entry;
			while ((entry = zis.getNextEntry()) != null) {
				int count;
				byte date[] = new byte[2048];
				if (entry.isDirectory()) {
					continue;
				} else {
					int begin = zipFileName.lastIndexOf(File.separator) + 1;
					int end = zipFileName.lastIndexOf(".");
					String zipRealName = zipFileName.substring(begin, end);

					String[] dirs = entry.getName().split("/",
							entry.getName().length());
					File file = null;
					if (isCurDir) {
						file = new File(outputDir);
					}else {
						file = new File(outputDir + File.separator + zipRealName);
					}
					if (dirs.length > 1) {
						for (int i = 0; i < dirs.length - 1; i++) {
							// 根据file抽象路径和dir路径字符串创建一个新的file对象，路径为文件的上一个目录
							file = new File(file, dirs[i]);
						}
					}
					if (!file.exists()) {
						file.mkdirs();
					}
					file = new File(file, dirs[dirs.length - 1]);

					bos = new BufferedOutputStream(new FileOutputStream(file));
					while ((count = zis.read(date)) != -1) {
						bos.write(date, 0, count);
					}
					bos.flush();
					bos.close();
				}
			}
			zis.close();
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}


	/**
	 * zip 解压
	 * 
	 * @param zipFileName
	 * @param outputDir
	 * @param charset
	 */
	public static void unzipFile(String zipFileName, String outputDir, String charset) {
		try {
			BufferedOutputStream bos = null;

			BufferedInputStream bis = new BufferedInputStream(new FileInputStream(zipFileName));
			ZipInputStream zis = new ZipInputStream(bis);
//			ZipInputStream zis = new ZipInputStream(bis,Charset.forName(charset));
			ZipEntry entry;
			while ((entry = zis.getNextEntry()) != null) {
				int count;
				byte date[] = new byte[2048];
				if (entry.isDirectory()) {
					continue;
				} else {
					int begin = zipFileName.lastIndexOf(File.separator) + 1;
					int end = zipFileName.lastIndexOf(".");
					String zipRealName = zipFileName.substring(begin, end);

					String[] dirs = entry.getName().split("/",
							entry.getName().length());
					File file = new File(outputDir + File.separator + zipRealName);
					if (dirs.length > 1) {
						for (int i = 0; i < dirs.length - 1; i++) {
							// 根据file抽象路径和dir路径字符串创建一个新的file对象，路径为文件的上一个目录
							file = new File(file, dirs[i]);
						}
					}
					if (!file.exists()) {
						file.mkdirs();
					}
					file = new File(file, dirs[dirs.length - 1]);

					bos = new BufferedOutputStream(new FileOutputStream(file));
					while ((count = zis.read(date)) != -1) {
						bos.write(date, 0, count);
					}
					bos.flush();
					bos.close();
				}
			}
			zis.close();
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	/**
	 * @param filecontent
	 *            base64的内容
	 * @param path
	 *            输出文件前缀路径
	 * @throws Exception
	 */
	public static void base64Unzip(String filecontent, String path)
			throws Exception {
		BASE64Decoder base64Decoder = new BASE64Decoder();
		byte[] auth = base64Decoder.decodeBuffer(filecontent);
		FileOutputStream fileOutputStream = null;
		try {
			ByteArrayInputStream bis = new ByteArrayInputStream(auth);
			ZipInputStream zip = new ZipInputStream(bis);
			ZipEntry entry = null;
			while ((entry = zip.getNextEntry()) != null) {
				byte[] buf = new byte[1024];
				int num = -1;
				fileOutputStream = new FileOutputStream(new File(path
						+ File.separator + entry.getName()));
				while ((num = zip.read(buf, 0, buf.length)) != -1) {
					fileOutputStream.write(buf, 0, num);
				}
			}
			bis.close();
			zip.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (fileOutputStream != null) {
				fileOutputStream.close();
			}
		}
	}

	/**
	 * 压缩成ZIP 方法1
	 * 
	 * @param srcDir
	 *            压缩文件夹路径
	 * @param out
	 *            压缩文件输出流
	 * @param KeepDirStructure
	 *            是否保留原来的目录结构,true:保留目录结构;
	 *            false:所有文件跑到压缩包根目录下(注意：不保留目录结构可能会出现同名文件,会压缩失败)
	 * @throws RuntimeException
	 *             压缩失败会抛出运行时异常
	 */
	public static void toZip(String srcDir, OutputStream out,
			boolean KeepDirStructure) throws RuntimeException {

		long start = System.currentTimeMillis();
		ZipOutputStream zos = null;
		try {
			zos = new ZipOutputStream(out);
			File sourceFile = new File(srcDir);
			compress(sourceFile, zos, sourceFile.getName(), KeepDirStructure);
			long end = System.currentTimeMillis();
			System.out.println("压缩完成，耗时：" + (end - start) + " ms");
		} catch (Exception e) {
			throw new RuntimeException("zip error from ZipUtils", e);
		} finally {
			if (zos != null) {
				try {
					zos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

	}

	/**
	 * 压缩成ZIP 方法2
	 * 
	 * @param srcFiles
	 *            需要压缩的文件列表
	 * @param out
	 *            压缩文件输出流
	 * @throws RuntimeException
	 *             压缩失败会抛出运行时异常
	 */
	public static void toZip(List<File> srcFiles, OutputStream out)
			throws RuntimeException {
		long start = System.currentTimeMillis();
		ZipOutputStream zos = null;
		try {
			zos = new ZipOutputStream(out);
			for (File srcFile : srcFiles) {
				byte[] buf = new byte[BUFFER_SIZE];
				zos.putNextEntry(new ZipEntry(srcFile.getName()));
				int len;
				FileInputStream in = new FileInputStream(srcFile);
				while ((len = in.read(buf)) != -1) {
					zos.write(buf, 0, len);
				}
				zos.closeEntry();
				in.close();
			}
			long end = System.currentTimeMillis();
			System.out.println("压缩完成，耗时：" + (end - start) + " ms");
		} catch (Exception e) {
			throw new RuntimeException("zip error from ZipUtils", e);
		} finally {
			if (zos != null) {
				try {
					zos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 递归压缩方法
	 * 
	 * @param sourceFile
	 *            源文件
	 * @param zos
	 *            zip输出流
	 * @param name
	 *            压缩后的名称
	 * @param KeepDirStructure
	 *            是否保留原来的目录结构,true:保留目录结构;
	 *            false:所有文件跑到压缩包根目录下(注意：不保留目录结构可能会出现同名文件,会压缩失败)
	 * @throws Exception
	 */
	private static void compress(File sourceFile, ZipOutputStream zos,
			String name, boolean KeepDirStructure) throws Exception {
		byte[] buf = new byte[BUFFER_SIZE];
		if (sourceFile.isFile()) {
			// 向zip输出流中添加一个zip实体，构造器中name为zip实体的文件的名字
			zos.putNextEntry(new ZipEntry(name));
			// copy文件到zip输出流中
			int len;
			FileInputStream in = new FileInputStream(sourceFile);
			while ((len = in.read(buf)) != -1) {
				zos.write(buf, 0, len);
			}
			// Complete the entry
			zos.closeEntry();
			in.close();
		} else {
			File[] listFiles = sourceFile.listFiles();
			if (listFiles == null || listFiles.length == 0) {
				// 需要保留原来的文件结构时,需要对空文件夹进行处理
				if (KeepDirStructure) {
					// 空文件夹的处理
					zos.putNextEntry(new ZipEntry(name + "/"));
					// 没有文件，不需要文件的copy
					zos.closeEntry();
				}
			} else {
				for (File file : listFiles) {
					// 判断是否需要保留原来的文件结构
					if (KeepDirStructure) {
						// 注意：file.getName()前面需要带上父文件夹的名字加一斜杠,
						// 不然最后压缩包中就不能保留原来的文件结构,即：所有文件都跑到压缩包根目录下了
						compress(file, zos, name + "/" + file.getName(),
								KeepDirStructure);
					} else {
						compress(file, zos, file.getName(), KeepDirStructure);
					}

				}
			}
		}
	}


	/**
	 * 带密码压缩
	 *
	 * @param inFile 需要压缩的单个文件
	 * @param destPath 压缩文件路径
	 * @param pwd        压缩文件密码
	 * @throws Exception 压缩失败会抛出运行时异常
	 */
	public static void toZipWithPwd(File inFile, String destPath, String pwd,String charset) throws Exception {

		AESEncrypter encrypter = new AESEncrypterBC();

		AesZipFileEncrypter zipEncrypter = null;
		try {
			zipEncrypter = new AesZipFileEncrypter(destPath, encrypter);
			zipEncrypter.setEncoding(charset);

			zipEncrypter.add(inFile, inFile.getName(), pwd);

		} catch (Exception e) {
			throw e;
		}finally {
			if (null != zipEncrypter) {
				zipEncrypter.close();
			}
		}
	}


	/**
	 * 带密码解压
	 *
	 * @param inFile 需要解压的文件
	 * @param outDir 解压到路径
	 * @param pwd        压缩文件密码
	 * @throws Exception 压缩失败会抛出运行时异常
	 */
	public static void unZipWithPwd(File inFile, File outDir, String pwd,String charset)  throws Exception {
		if (outDir == null) {
			outDir = inFile.getParentFile();
		}
		if (!outDir.exists()) {
			outDir.mkdirs();
		}
		/*解密方式*/
		AESDecrypter decrypter = new AESDecrypterBC();
		AesZipFileDecrypter zipDecrypter = null;
		try {
			/*初始化解密文件与解密方式*/
			zipDecrypter = new AesZipFileDecrypter(inFile, decrypter);
			/*设置字符集*/
			AesZipFileDecrypter.charset = charset;
			List<ExtZipEntry> entryList = zipDecrypter.getEntryList();
			for (ExtZipEntry entry : entryList) {
				String eName = entry.getName();
				String dir = eName.substring(0, eName.lastIndexOf(File.separator) + 1);
				File extractDir = new File(outDir, dir);
				if (!extractDir.exists()) {
					FileUtils.forceMkdir(extractDir);
				}

				File extractFile = new File(outDir + File.separator + eName);
				zipDecrypter.extractEntry(entry, extractFile, pwd);
			}
		} catch (Exception e) {
			throw e;
		}finally {
			if (zipDecrypter != null) {
				zipDecrypter.close();
			}

		}
	}
}
